// DirectXTK MakeSpriteFont tool
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// http://go.microsoft.com/fwlink/?LinkId=248929

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

namespace MakeSpriteFont
{
    // Assorted helpers for doing useful things with bitmaps.
    public static class BitmapUtils
    {
        // Copies a rectangular area from one bitmap to another.
        public static void CopyRect(Bitmap source, Rectangle sourceRegion, Bitmap output, Rectangle outputRegion)
        {
            if (sourceRegion.Width != outputRegion.Width ||
                sourceRegion.Height != outputRegion.Height)
            {
                throw new ArgumentException();
            }

            using (var sourceData = new PixelAccessor(source, ImageLockMode.ReadOnly, sourceRegion))
            using (var outputData = new PixelAccessor(output, ImageLockMode.WriteOnly, outputRegion))
            {
                for (int y = 0; y < sourceRegion.Height; y++)
                {
                    for (int x = 0; x < sourceRegion.Width; x++)
                    {
                        outputData[x, y] = sourceData[x, y];
                    }
                }
            }
        }


        // Checks whether an area of a bitmap contains entirely the specified alpha value.
        public static bool IsAlphaEntirely(byte expectedAlpha, Bitmap bitmap, Rectangle? region = null)
        {
            using (var bitmapData = new PixelAccessor(bitmap, ImageLockMode.ReadOnly, region))
            {
                for (int y = 0; y < bitmapData.Region.Height; y++)
                {
                    for (int x = 0; x < bitmapData.Region.Width; x++)
                    {
                        byte alpha = bitmapData[x, y].A;

                        if (alpha != expectedAlpha)
                            return false;
                    }
                }
            }

            return true;
        }


        // Checks whether a bitmap contains entirely the specified RGB value.
        public static bool IsRgbEntirely(Color expectedRgb, Bitmap bitmap)
        {
            using (var bitmapData = new PixelAccessor(bitmap, ImageLockMode.ReadOnly))
            {
                for (int y = 0; y < bitmap.Height; y++)
                {
                    for (int x = 0; x < bitmap.Width; x++)
                    {
                        Color color = bitmapData[x, y];

                        if (color.A == 0)
                            continue;

                        if ((color.R != expectedRgb.R) ||
                            (color.G != expectedRgb.G) ||
                            (color.B != expectedRgb.B))
                        {
                            return false;
                        }
                    }
                }
            }

            return true;
        }


        // Converts greyscale luminosity to alpha data.
        public static void ConvertGreyToAlpha(Bitmap bitmap)
        {
            using (var bitmapData = new PixelAccessor(bitmap, ImageLockMode.ReadWrite))
            {
                for (int y = 0; y < bitmap.Height; y++)
                {
                    for (int x = 0; x < bitmap.Width; x++)
                    {
                        Color color = bitmapData[x, y];

                        // Average the red, green and blue values to compute brightness.
                        int alpha = (color.R + color.G + color.B) / 3;

                        bitmapData[x, y] = Color.FromArgb(alpha, 255, 255, 255);
                    }
                }
            }
        }


        // Converts a bitmap to premultiplied alpha format.
        public static void PremultiplyAlpha(Bitmap bitmap)
        {
            using (var bitmapData = new PixelAccessor(bitmap, ImageLockMode.ReadWrite))
            {
                for (int y = 0; y < bitmap.Height; y++)
                {
                    for (int x = 0; x < bitmap.Width; x++)
                    {
                        Color color = bitmapData[x, y];

                        int a = color.A;
                        int r = color.R * a / 255;
                        int g = color.G * a / 255;
                        int b = color.B * a / 255;

                        bitmapData[x, y] = Color.FromArgb(a, r, g, b);
                    }
                }
            }
        }


        // To avoid filtering artifacts when scaling or rotating fonts that do not use premultiplied alpha,
        // make sure the one pixel border around each glyph contains the same RGB values as the edge of the
        // glyph itself, but with zero alpha. This processing is an elaborate no-op when using premultiplied
        // alpha, because the premultiply conversion will change the RGB of all such zero alpha pixels to black.
        public static void PadBorderPixels(Bitmap bitmap, Rectangle region)
        {
            using (var bitmapData = new PixelAccessor(bitmap, ImageLockMode.ReadWrite))
            {
                // Pad the top and bottom.
                for (int x = region.Left; x < region.Right; x++)
                {
                    CopyBorderPixel(bitmapData, x, region.Top, x, region.Top - 1);
                    CopyBorderPixel(bitmapData, x, region.Bottom - 1, x, region.Bottom);
                }

                // Pad the left and right.
                for (int y = region.Top; y < region.Bottom; y++)
                {
                    CopyBorderPixel(bitmapData, region.Left, y, region.Left - 1, y);
                    CopyBorderPixel(bitmapData, region.Right - 1, y, region.Right, y);
                }

                // Pad the four corners.
                CopyBorderPixel(bitmapData, region.Left, region.Top, region.Left - 1, region.Top - 1);
                CopyBorderPixel(bitmapData, region.Right - 1, region.Top, region.Right, region.Top - 1);
                CopyBorderPixel(bitmapData, region.Left, region.Bottom - 1, region.Left - 1, region.Bottom);
                CopyBorderPixel(bitmapData, region.Right - 1, region.Bottom - 1, region.Right, region.Bottom);
            }
        }


        // Copies a single pixel within a bitmap, preserving RGB but forcing alpha to zero.
        static void CopyBorderPixel(PixelAccessor bitmapData, int sourceX, int sourceY, int destX, int destY)
        {
            Color color = bitmapData[sourceX, sourceY];

            bitmapData[destX, destY] = Color.FromArgb(0, color);
        }

        
        // Converts a bitmap to the specified pixel format.
        public static Bitmap ChangePixelFormat(Bitmap bitmap, PixelFormat format)
        {
            Rectangle bounds = new Rectangle(0, 0, bitmap.Width, bitmap.Height);

            return bitmap.Clone(bounds, format);
        }


        // Helper for locking a bitmap and efficiently reading or writing its pixels.
        public sealed class PixelAccessor : IDisposable
        {
            // Constructor locks the bitmap.
            public PixelAccessor(Bitmap bitmap, ImageLockMode mode, Rectangle? region = null)
            {
                this.bitmap = bitmap;

                this.Region = region.GetValueOrDefault(new Rectangle(0, 0, bitmap.Width, bitmap.Height));

                this.data = bitmap.LockBits(Region, mode, PixelFormat.Format32bppArgb);
            }


            // Dispose unlocks the bitmap.
            public void Dispose()
            {
                if (data != null)
                {
                    bitmap.UnlockBits(data);

                    data = null;
                }
            }


            // Query what part of the bitmap is locked.
            public Rectangle Region { get; private set; }


            // Get or set a pixel value.
            public Color this[int x, int y]
            {
                get
                {
                    return Color.FromArgb(Marshal.ReadInt32(PixelAddress(x, y)));
                }

                set
                {
                    Marshal.WriteInt32(PixelAddress(x, y), value.ToArgb()); 
                }
            }


            // Helper computes the address of the specified pixel.
            IntPtr PixelAddress(int x, int y)
            {
                return data.Scan0 + (y * data.Stride) + (x * sizeof(int));
            }


            // Fields.
            Bitmap bitmap;
            BitmapData data;
        }
    }
}
